package com.lijiajia.cloud.common.security.access;

import com.lijiajia.cloud.common.security.access.prepost.CustomPrePostAnnotationSecurityMetadataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.annotation.Jsr250Voter;
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice;
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 自定义 方法 权限配置
 *
 * @author lijiajia
 * @since 2019-05-31
 */

// @Configuration
public class CustomMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
    private static final Log logger = LogFactory
            .getLog(GlobalMethodSecurityConfiguration.class);

    private AnnotationAttributes enableMethodSecurity;
    private BeanFactory context;

    /**
     * Provides the default {@link MethodSecurityMetadataSource} that will be used. It
     * creates a {@link DelegatingMethodSecurityMetadataSource} based upon
     * {@link #customMethodSecurityMetadataSource()} and the attributes on
     * {@link EnableGlobalMethodSecurity}.
     *
     * @return the {@link MethodSecurityMetadataSource}
     */
    @Bean
    public MethodSecurityMetadataSource methodSecurityMetadataSource2() {
        List<MethodSecurityMetadataSource> sources = new ArrayList<>();
        ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
                getExpressionHandler());
        MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
        if (customMethodSecurityMetadataSource != null) {
            sources.add(customMethodSecurityMetadataSource);
        }

        boolean hasCustom = customMethodSecurityMetadataSource != null;
        boolean isPrePostEnabled = prePostEnabled();
        boolean isSecuredEnabled = securedEnabled();
        boolean isJsr250Enabled = jsr250Enabled();

        if (!isPrePostEnabled && !isSecuredEnabled && !isJsr250Enabled && !hasCustom) {
            throw new IllegalStateException("In the composition of all global method configuration, " +
                    "no annotation support was actually activated");
        }

        if (isPrePostEnabled) {
            sources.add(new CustomPrePostAnnotationSecurityMetadataSource(attributeFactory));
        }
        if (isSecuredEnabled) {
            sources.add(new SecuredAnnotationSecurityMetadataSource());
        }
        if (isJsr250Enabled) {
            GrantedAuthorityDefaults grantedAuthorityDefaults =
                    getSingleBeanOrNull(GrantedAuthorityDefaults.class);
            Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context.getBean(Jsr250MethodSecurityMetadataSource.class);
            if (grantedAuthorityDefaults != null) {
                jsr250MethodSecurityMetadataSource.setDefaultRolePrefix(
                        grantedAuthorityDefaults.getRolePrefix());
            }
            sources.add(jsr250MethodSecurityMetadataSource);
        }
        return new DelegatingMethodSecurityMetadataSource(sources);
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.context = beanFactory;
    }

    private boolean prePostEnabled() {
        return enableMethodSecurity().getBoolean("prePostEnabled");
    }

    private boolean securedEnabled() {
        return enableMethodSecurity().getBoolean("securedEnabled");
    }

    private boolean jsr250Enabled() {
        return enableMethodSecurity().getBoolean("jsr250Enabled");
    }

    private boolean isAspectJ() {
        return enableMethodSecurity().getEnum("mode") == AdviceMode.ASPECTJ;
    }

    private AnnotationAttributes enableMethodSecurity() {
        if (enableMethodSecurity == null) {
            // if it is null look at this instance (i.e. a subclass was used)
            EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils
                    .findAnnotation(getClass(), EnableGlobalMethodSecurity.class);
            Assert.notNull(methodSecurityAnnotation,
                    () -> EnableGlobalMethodSecurity.class.getName() + " is required");
            Map<String, Object> methodSecurityAttrs = AnnotationUtils
                    .getAnnotationAttributes(methodSecurityAnnotation);
            this.enableMethodSecurity = AnnotationAttributes.fromMap(methodSecurityAttrs);
        }
        return this.enableMethodSecurity;
    }

    private <T> T getSingleBeanOrNull(Class<T> type) {
        try {
            return context.getBean(type);
        } catch (NoSuchBeanDefinitionException e) {
        }
        return null;
    }
}
